
public class MainLoop implements Runnable {

    public static final int DEFAULT_UPS = 80;
    public static final int DEFAULT_NO_DELAYS_PER_YIELD = 16;
    public static final int DEFAULT_MAX_FRAME_SKIPS = 5;
    private LoopSteps game;
    private long desiredUpdateTime;
    private boolean running=false;
    private long afterTime;
    private long beforeTime = System.currentTimeMillis();
    private long overSleepTime = 0;
    private long excessTime = 0;
    private int noDelaysPerYield = DEFAULT_NO_DELAYS_PER_YIELD;
    private int maxFrameSkips = DEFAULT_MAX_FRAME_SKIPS;
    int noDelays = 0;
    private boolean paused = false;

    public MainLoop(LoopSteps loopSteps,
            int ups, int maxFrameSkips, int noDelaysPerYield) {
        super();
        if (ups < 1) {
            throw new IllegalArgumentException("You must display at least one frame per second!");
        }

        if (ups > 1000) {
            ups = 1000;
        }

        this.game = loopSteps;
        this.desiredUpdateTime = 1000000000L / ups;
        this.running = false;
        this.maxFrameSkips = maxFrameSkips;
        this.noDelaysPerYield = noDelaysPerYield;
    }

    public MainLoop(LoopSteps loopSteps, int ups) {
        this(loopSteps, ups, DEFAULT_MAX_FRAME_SKIPS, DEFAULT_NO_DELAYS_PER_YIELD);
    }

    public MainLoop(LoopSteps loopSteps) {
        this(loopSteps, DEFAULT_UPS);
    }

    private void sleep(long nanos) {
        try {
            noDelays = 0;
            long beforeSleep = System.currentTimeMillis();
            Thread.sleep(nanos);
            overSleepTime = System.currentTimeMillis() - beforeSleep - nanos;
        } catch (Exception e) {
        }
    }

    private void yieldIfNeed() {
        if (++noDelays == noDelaysPerYield) {
            Thread.yield();
            noDelays = 0;
        }
    }

    private long calculateSleepTime() {
        return desiredUpdateTime - (afterTime - beforeTime) - overSleepTime;
    }

    public void run() {
        running = true;

        try {
            game.setup();
            while (running) {

                synchronized (this) {
                    if (paused) {
                        this.wait();
                    }
                }

                beforeTime = System.currentTimeMillis();
                skipFramesInExcessTime();

                // Updates, renders and paint the screen
                game.processLogics();
                game.renderGraphics();
                game.paintScreen();

                afterTime = System.currentTimeMillis();
                long sleepTime = calculateSleepTime();
                if (sleepTime >= 0) {
                    sleep(sleepTime);
                } else {
                    excessTime -= sleepTime; // Sleep time is negative
                    overSleepTime = 0L;
                    yieldIfNeed();
                }
            }
        } catch (Exception e) {
          e.printStackTrace();
        } finally {
            running = false;
            game.tearDown();
        }
    }

    private void skipFramesInExcessTime() {
        int skips = 0;
        while ((excessTime > desiredUpdateTime) && (skips < maxFrameSkips)) {
            excessTime -= desiredUpdateTime;
            game.processLogics();
            skips++;
        }
    }

    public void stop() {
        running = false;
    }

    /**
     * Resumes the Game.
     */
    public void resume() {
        synchronized (this) {
            if (paused) {
                this.paused = false;
            }
            this.notifyAll();
        }
    }

    /**
     * Pauses the Game.
     */
    public void pause() {
        synchronized (this) {
            if (!paused) {
                this.paused = true;
            }
        }
    }

    public void setDesiredUpdateTime(long desiredUpdateTime) {
        this.desiredUpdateTime = desiredUpdateTime;
    }

    public boolean isPaused() {
        return paused;
    }

    public boolean isRunning() {
        return running;
    }
}
